﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using System.Reflection;
using System.IO;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Collections;
using AbstraX.Contracts;
using Utils;
using Metaspec;
using HydraPackageToolImp.Templates;

namespace HydraPackageTool.Generators
{
    /// <summary>
    /// </summary>
    public class TemplateEngineHost
    {
        private Type generatorType;
        public event ResolveTypeHandler ResolveType;

        public byte[] GenerateFile<TGeneratorType>(string _namespace, CsNode node)
        {
            this.generatorType = typeof(TGeneratorType);

            return this.GenerateFile(_namespace, node);
        }

        public byte[] GenerateFile(string _namespace, CsNode node)
        {
            try
            {
                dynamic codeGenerator = Activator.CreateInstance(generatorType);
                var session = new TextTemplatingSession();
                var stream = new MemoryStream();
                var bytes = new byte[0];

                if (node is CsCompilationUnit)
                {
                    var compilationUnit = (CsCompilationUnit)node;
                    var namespaces = new List<string>();

                    if (compilationUnit.using_directives != null)
                    {
                        namespaces.AddRange(compilationUnit.using_directives.OfType<CsUsingNamespaceDirective>().Select(u => 
                        {
                            var namespaceText = string.Empty;
                            var namespaceOrType = ((CsNamespaceOrTypeName)u.namespace_or_type_name);

                            while (namespaceOrType != null)
                            {
                                namespaceText = namespaceOrType.identifier.identifier + (namespaceText.Length > 0 ? ("." + namespaceText) : "");
                                namespaceOrType = (CsNamespaceOrTypeName) namespaceOrType.namespace_or_type_name;
                            }

                            return namespaceText;
                        }));
                    }

                    foreach (CsNamespace namespaceNode in compilationUnit.declarations)
                    {
                        var namespaceText = string.Empty;

                        namespaceNode.qualified_identifier.ToList().ForEach(i => namespaceText += i.identifier.identifier + ".");
                        namespaceText = namespaceText.RemoveEnd(1);

                        namespaces.Add(namespaceText);

                        if (namespaceNode.using_directives != null)
                        {
                            namespaces.AddRange(namespaceNode.using_directives.OfType<CsUsingNamespaceDirective>().Select(u =>
                            {
                                var namespaceOrType = ((CsNamespaceOrTypeName)u.namespace_or_type_name);
                                
                                namespaceText = string.Empty;

                                while (namespaceOrType != null)
                                {
                                    namespaceText = namespaceOrType.identifier.identifier + (namespaceText.Length > 0 ? ("." + namespaceText) : "");
                                    namespaceOrType = (CsNamespaceOrTypeName)namespaceOrType.namespace_or_type_name;
                                }

                                return namespaceText;
                            }));
                        }

                        foreach (CsClass classNode in namespaceNode.member_declarations)
                        {
                            var memberNodes = classNode.member_declarations;

                            foreach (var propertyNode in memberNodes.OfType<CsProperty>())
                            {
                                var attributes = propertyNode.attributes;

                                foreach (var attributeSection in attributes.sections)
                                {
                                    foreach (var attribute in attributeSection.attribute_list)
                                    {
                                        if (attribute.attribute_name is CsNamespaceOrTypeName)
                                        {
                                            var typeName = (CsNamespaceOrTypeName)attribute.attribute_name;
                                            var type = typeName.identifier.identifier;

                                            if (type == "Association")
                                            {
                                                if (propertyNode.type.type_name is CsNamespaceOrTypeName)
                                                {
                                                    var propertyTypeName = (CsNamespaceOrTypeName)propertyNode.type.type_name;
                                                    var propertyType = propertyTypeName.identifier.identifier;

                                                    if (propertyType.IsOneOf("List", "IQueryable", "IEnumerable") && propertyTypeName.type_argument_list.list.Count == 1)
                                                    {
                                                        var argTypeName = (CsNamespaceOrTypeName) propertyTypeName.type_argument_list.list.First().type_name;
                                                        var argType = argTypeName.identifier.identifier;

                                                        ResolveType(this, new ResolveTypeArgs(argType, namespaces));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    session["Namespace"] = _namespace;
                    session["CompilationUnit"] = compilationUnit;

                    codeGenerator.Session = session;
                    codeGenerator.Initialize();

                    var output = (string)codeGenerator.TransformText();

                    bytes = UTF8Encoding.UTF8.GetBytes(output);
                }

                return bytes;
            }
            catch (Exception ex)
            {
                Debugger.Break();

                var output = ex.Message + "\r\n" + ex.StackTrace;
                var bytes = UTF8Encoding.UTF8.GetBytes(output);

                return bytes;
            }
        }
    }
}
